﻿using FP.Automation.Utilities;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;

namespace FP.Automation.WorkItemMigration
{
	public class WIMigrator
	{
		#region Constructors
		
		public WIMigrator(string projectCollectionUrl, WISourceInfo sourceWorkItemInfo, WITargetInfo targetWorkItemInfo)
		{
			this._Logger = SLogger.Instance;

			try
			{
				this._Configuration = new MigrationConfiguration(projectCollectionUrl, sourceWorkItemInfo, targetWorkItemInfo);
				this._ProcessedIds = new List<int>();
				this._MigrationErrorCount = 0;
				this._ReadyToMigrate = true;
			}
			catch (Exception ex)
			{
				this._Logger.Error("Constructor generated an Exception");
				this._Logger.Fatal(ex.Message);
			}
		}

		public WIMigrator(MigrationConfiguration configuration)
		{
			this._Logger = SLogger.Instance;
			
			try
			{
				this._Configuration = configuration;
				this._ProcessedIds = new List<int>();
				this._MigrationErrorCount = 0;
				this._ReadyToMigrate = true;
			}
			catch (Exception ex)
			{
				this._Logger.Error("Constructor generated an Exception");
				this._Logger.Fatal(ex.Message);
			}
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Starts the process of migrating the work item and / or it's links
		/// </summary>
		/// <returns>returns true if the work item is created, validated, and saved</returns>
		public bool Migrate()
		{
			if (_ReadyToMigrate)
			{
				try
				{
					//setup for migration
					this._Logger.Info(
						"Starting Migration Of Work Item Id {0} From {1} To {2}",
						this.Configuration.SourceItemInfo.WorkItemId,
						this.Configuration.SourceItemInfo.TeamProjectName, 
						this.Configuration.TargetItemInfo.TeamProjectName
					);

					//set properties on migrator
					this.TeamProjectCollection = GetTfsTeamProjectCollection(this.Configuration.ProjectCollectionUri);
					this.ItemStore = GetWorkItemStore(this.TeamProjectCollection);
					
					//save source item as a member of Configuration.SourceItemInfo
					this.Configuration.SourceItemInfo.TfsWorkItem = this.ItemStore.GetWorkItem(this.SourceInfo.WorkItemId);

					//validate the item resides in the correct project
					if (ValidateWorkItemProject(this.Configuration.SourceItemInfo.TeamProjectName, this.Configuration.SourceItemInfo.TfsWorkItem.Project.Name))
					{
						return ProcessWorkItem(this.SourceInfo.TfsWorkItem);
					}
					else
					{
						return false;
					}
				}
				catch (Exception ex)
				{
					_Logger.Warn(
						"Migrating {0} Exception Caught {1}",
						this.Configuration.SourceItemInfo.WorkItemId,
						ex.Message
					);
					return false;
				}
			}
			else
			{
				_Logger.Error("WIMigrator Private Member _ReadyToMigrate Returned False. Exiting!");
				return false;
			}
		}

		/// <summary>
		/// returns a new work item variant based on the Process Template defined for the Project 
		/// </summary>
		/// <param name="workItemTypeName">the string name of the distinct work item type you want</param>
		/// <returns>returns null if work item type does not exist for the team project</returns>
		public WorkItem GetNewWorkItem(string workItemTypeName)
		{
			return GetNewWorkItem(TeamProjectCollection, TargetInfo.TeamProjectName, workItemTypeName);
		}

		/// <summary>
		/// returns a new work item variant based on the Process Template defined for the Project
		/// </summary>
		/// <param name="tfsTeamProjectCollection">the tfs project collection reference</param>
		/// <param name="teamProjectName">the name of the team project to create the new work item.</param>
		/// <param name="workItemTypeName">the name of the type of work item to create</param>
		/// <returns>returns null if work item type does not exist for the team project</returns>
		public WorkItem GetNewWorkItem(TfsTeamProjectCollection tfsTeamProjectCollection, string teamProjectName, string workItemTypeName)
		{
			try
			{
				return GetWorkItemStore(tfsTeamProjectCollection).Projects[teamProjectName].WorkItemTypes[workItemTypeName].NewWorkItem();
			}
			catch (Exception ex)
			{
				_Logger.Error("Failed to create a new Work Item in Team Project {0} with wi type Name {1}", teamProjectName, workItemTypeName);
				_Logger.Fatal(ex.Message);
				return null;
			}
		}

		/// <summary>
		/// retrieve an existing work item reference from a team project
		/// </summary>
		/// <param name="teamProjectName">name of the team project to ask for the work item</param>
		/// <param name="workItemId">the Id of the work item to retrieve</param>
		/// <returns>returns null if the work item does not exist</returns>
		public WorkItem GetExistingWorkItem(string teamProjectName, int workItemId)
		{
			return GetExistingWorkItem(TeamProjectCollection, teamProjectName, workItemId);
		}

		public WorkItem GetExistingWorkItem(TfsTeamProjectCollection tfsTeamProjectCollection, string teamProjectName, int workItemId)
		{
			try
			{
				var eWorkItem = this.ItemStore.GetWorkItem(workItemId);
				if (eWorkItem != null && eWorkItem.Project.Name == teamProjectName)
				{
					_Logger.Debug("Work Item Id {0} was found in Team Project {1} returning Item", workItemId, teamProjectName);
					return eWorkItem;
				}
				else
				{
					_Logger.Debug("The Work Item Id {0} was not found in Team Project {1} returning null", workItemId, teamProjectName);
					return null;
				}
			}
			catch (Exception ex)
			{
				_Logger.Error("Failed to get Work Item Id {0}", workItemId);
				_Logger.Fatal(ex.Message);
			}
			return null;
		}

		public WorkItem GetExistingWorkItem(string teamProjectName, string workItemTitle)
		{
			return GetExistingWorkItem(TeamProjectCollection, teamProjectName, workItemTitle);
		}

		public WorkItem GetExistingWorkItem(TfsTeamProjectCollection tfsTeamProjectCollection, string teamProjectName, string workItemTitle)
		{
			try
			{
				var ewi = 
					from WorkItem wi
					in WorkItemQuery(workItemTitle, teamProjectName).RunQuery()
					select wi;
				var eWorkItem = ewi.FirstOrDefault();
				if (eWorkItem != null && eWorkItem.Project.Name == teamProjectName)
				{
					_Logger.Debug("Work Item Title {0} was found in Team Project {1} returning Item", workItemTitle, teamProjectName);
					return eWorkItem;
				}
				else
				{
					_Logger.Debug("The Work Item Title {0} was not found in Team Project {1} returning null", workItemTitle, teamProjectName);
					return null;
				}
			}
			catch (Exception ex)
			{
				_Logger.Error("Failed to get Work Item Title {0}", workItemTitle);
				_Logger.Fatal(ex.Message);
			}
			return null;
		}

		public TfsTeamProjectCollection GetTfsTeamProjectCollection(Uri projectCollectionUri)
		{
			var collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(projectCollectionUri);

			try
			{
				collection.Connect(ConnectOptions.IncludeServices);
				collection.Authenticate();
			}
			catch (Exception ex)
			{
				_Logger.Error("Connecting to TFS @ {0} Failed.", projectCollectionUri.ToString());
				_Logger.Fatal(ex.Message);
			}

			return collection;
		}

		public WorkItemStore GetWorkItemStore(TfsTeamProjectCollection teamProjectCollection)
		{
			return teamProjectCollection.GetService<WorkItemStore>();
		}

		public ICommonStructureService GetCommonStructureService(TfsTeamProjectCollection teamProjectCollection)
		{
			return teamProjectCollection.GetService<ICommonStructureService>();
		}

		public bool ValidateWorkItemProject(string definedProjectName, string itemsProjectName)
		{
			if (definedProjectName == itemsProjectName)
			{
				this._Logger.Info("Defined Team Project Name Matches The Work Items Project Name");
				return true;
			}
			else
			{
				_Logger.Error(
					"The Work Item Id {0} is from team project {1} but you specified {2} as the source team project.",
					this.Configuration.SourceItemInfo.TfsWorkItem.Id,
					this.Configuration.SourceItemInfo.TfsWorkItem.Project.Name,
					this.Configuration.SourceItemInfo.TeamProjectName
				);
				return false;
			}	
		}

		public bool ProcessWorkItem(WorkItem sourceWorkItem)
		{
			var result = true;

			if (_ProcessedIds.Contains(sourceWorkItem.Id))
			{
				return result;
			}
			else
			{
				_ProcessedIds.Add(sourceWorkItem.Id);
			}
			
			UInt32 migrationStatus = CanItemBeMigrated(sourceWorkItem.Id, sourceWorkItem.Project.Name, this.TargetInfo.TeamProjectName);

			if (migrationStatus == UInt32.MaxValue)
			{
				_Logger.Error("Failed To Find The Source Work Item {0} From Team Project {1}", sourceWorkItem.Id, sourceWorkItem.Project.Name);
				result = false;
			}
			else if (migrationStatus == UInt32.MinValue)
			{
				PathStructureStatus areaStatus = PathStructureStatus.Exists;
				PathStructureStatus iterationStatus = PathStructureStatus.Exists;

				//ensure the area path exists on target if indicated.
				if (this.TargetInfo.CreateAreaPath)
				{
					areaStatus = EnsureAreaPathExists(this.TargetInfo.TeamProjectName, sourceWorkItem.AreaPath);
				}

				//ensure the iteration either user defined or copied from source exists on target if indicated.
				if ((!string.IsNullOrEmpty(this.TargetInfo.IterationPath)) && (this.TargetInfo.IterationPath.StartsWith(this.TargetInfo.TeamProjectName)))
				{
					iterationStatus = EnsureIterationPathExists(this.TargetInfo.IterationPath);
				}
				else if ((!string.IsNullOrEmpty(this.TargetInfo.IterationPath)) && (this.TargetInfo.IterationPath.ToLower().Trim() == "copyfromsource"))
				{
				 	this.TargetInfo.IterationPath = sourceWorkItem.IterationPath.Replace(this.SourceInfo.TeamProjectName, this.TargetInfo.TeamProjectName).Trim();
					iterationStatus = EnsureIterationPathExists(this.TargetInfo.IterationPath);
				}
				
				//if the Area or Iteration paths were created pause the app
				if (areaStatus == PathStructureStatus.Created || iterationStatus == PathStructureStatus.Created)
				{
					_Logger.Info("Sleeping for one minute to allow the created paths to update.");
					var sleepTime = 60000;
					System.Threading.Thread.Sleep(sleepTime);
				}

				switch (sourceWorkItem.Type.Name.ToLower())
				{
					case "bug":
						result = MigrateBugToBug(sourceWorkItem);
						break;
					case "user story":
						SourceInfo.MigrateRelatedWorkItems = true;
						if (TargetInfo.TemplateType == ProcessTemplateType.Scrum)
						{
							result = MigrateUserStoryToPBI(sourceWorkItem);
						}
						else if(TargetInfo.TemplateType == ProcessTemplateType.Agile)
						{
							result = MigrateUserStoryToUserStory(sourceWorkItem);
						}
						break;
					case "task":
						result = MigrateTaskToTask(sourceWorkItem);
						break;
					case "test case":
						result = MigrateTestCaseToTestCase(sourceWorkItem);
						break;
					case "shared steps":
						result = MigrateSharedStepsToSharedSteps(sourceWorkItem);
						break;
					case "issue":
						if (TargetInfo.TemplateType == ProcessTemplateType.Agile)
						{
							result = MigrateIssueToIssue(sourceWorkItem);
						}
						else
						{
							_Logger.Warn("Only Support Migrating Issues to Agile Team Projects At This Time.");
							TargetInfo.WorkItemId = 0;
							result = true;
						}
						break;
					default:
						_Logger.Warn("Migrating Work Item Type {0} Not Supported", sourceWorkItem.Type.Name);
						TargetInfo.WorkItemId = 0;
						result = true;
						break;
				}
			}
			else if (migrationStatus > UInt32.MinValue && migrationStatus < UInt32.MaxValue)
			{
				_Logger.Warn("The Work Item {0} Already Migrated to Work Item {1}", sourceWorkItem.Id, migrationStatus);
				TargetInfo.WorkItemId = (Int32)migrationStatus;
				result = true;
			}

			if (result && TargetInfo.WorkItemId != 0)
			{
				this.TargetInfo.TfsWorkItem = this.ItemStore.GetWorkItem(this.TargetInfo.WorkItemId);

				if (this.TargetInfo.SetState)
				{
					result = SetWorkItemState(this.TargetInfo, sourceWorkItem.State, SourceInfo.TemplateType);
				}

				result = CopyWorkItemLinks(sourceWorkItem, this.TargetInfo.TfsWorkItem);

				if (result && SourceInfo.DestroyAfterMigrationSuccess)
				{
					result = DestroyWorkItem(sourceWorkItem);
				}
			}
			else if (result && TargetInfo.WorkItemId == 0)
			{
				_Logger.Warn("Work Item {0} was skipped due to migration incompatable Process Templates and Work Item Types.", SourceInfo.WorkItemId);
			}
			else
			{
				_Logger.Error("Work Item {0} Migration failed. The new work item failed validation for Team Project {1}, Check the logs for details", sourceWorkItem.Id, TargetInfo.TeamProjectName);
			}

			return result;
		}

		#endregion

		#region Private Migration Methods

		private WorkItem InitializeWorkItem(WorkItem sourceWorkItem, WorkItem targetWorkItem)
		{
			targetWorkItem.Title = sourceWorkItem.Title;
			targetWorkItem.Description = sourceWorkItem.Description;
			targetWorkItem.History = BuildRevisionHistory(sourceWorkItem.Revisions);
			targetWorkItem.Fields["Microsoft.VSTS.Build.IntegrationBuild"].Value = 
			targetWorkItem.Fields["System.AssignedTo"].Value = sourceWorkItem.Fields["System.AssignedTo"].Value;

			var aPath = sourceWorkItem.AreaPath.Replace(sourceWorkItem.Project.Name, targetWorkItem.Project.Name);
			if (this.TargetInfo.CreateAreaPath && EnsureAreaPathExists(targetWorkItem.Project.Name, sourceWorkItem.AreaPath) == PathStructureStatus.Exists)
			{
				targetWorkItem.AreaPath = aPath;
			}
			
			var iPath = sourceWorkItem.IterationPath.Replace(sourceWorkItem.Project.Name, targetWorkItem.Project.Name);
			if(!string.IsNullOrEmpty(this.TargetInfo.IterationPath) && EnsureIterationPathExists(iPath) == PathStructureStatus.Exists)
			{
				targetWorkItem.IterationPath = iPath;
			}

			return targetWorkItem;
		}

		private bool MigrateIssueToIssue(WorkItem sourceWorkItem)
		{
			try
			{
				var NewIssue = GetNewWorkItem("Issue");
				NewIssue = InitializeWorkItem(sourceWorkItem, NewIssue);

				if (CopyAttachments(sourceWorkItem, NewIssue))
				{
					_Logger.Info("All Attachments Were Migrated");
				}
				else
				{
					_Logger.Warn("There Was An Problem Migrating One Or More Attachments");
				}

				return ValidateWorkItem(NewIssue);
			}
			catch (Exception ex)
			{
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool MigrateSharedStepsToSharedSteps(WorkItem sourceWorkItem)
		{
			try
			{
				var NewSharedSteps = GetNewWorkItem("Shared Steps");
				NewSharedSteps = InitializeWorkItem(sourceWorkItem, NewSharedSteps);
				
				NewSharedSteps.Fields["Microsoft.VSTS.TCM.Parameters"].Value = sourceWorkItem.Fields["Microsoft.VSTS.TCM.Parameters"].Value;
				NewSharedSteps.Fields["Microsoft.VSTS.TCM.Steps"].Value = sourceWorkItem.Fields["Microsoft.VSTS.TCM.Steps"].Value;
				NewSharedSteps.Fields["Microsoft.VSTS.Common.Issue"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Common.Issue"].Value;
				NewSharedSteps.Fields["Microsoft.VSTS.Common.Priority"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Common.Priority"].Value;
				NewSharedSteps.Fields["System.CreatedBy"].Value = sourceWorkItem.Fields["System.CreatedBy"].Value;
				
				if(CopyAttachments(sourceWorkItem,NewSharedSteps))
				{
					_Logger.Info("All Attachments Were Migrated");
				}
				else
				{
					_Logger.Warn("There Was An Problem Migrating One Or More Attachments");
				}
				
				return ValidateWorkItem(NewSharedSteps);
			}
			catch (Exception ex)
			{
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool MigrateTestCaseToTestCase(WorkItem sourceWorkItem)
		{
			try
			{
				var NewTestCase = GetNewWorkItem("Test Case");
				NewTestCase = InitializeWorkItem(sourceWorkItem, NewTestCase);

				NewTestCase.Fields["Microsoft.VSTS.TCM.Parameters"].Value = sourceWorkItem.Fields["Microsoft.VSTS.TCM.Parameters"].Value;
				NewTestCase.Fields["Microsoft.VSTS.TCM.Steps"].Value = sourceWorkItem.Fields["Microsoft.VSTS.TCM.Steps"].Value;
				NewTestCase.Fields["Microsoft.VSTS.Common.Priority"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Common.Priority"].Value;
				NewTestCase.Fields["System.CreatedBy"].Value = NewTestCase.Fields["System.CreatedBy"].Value;
				
				if(CopyAttachments(sourceWorkItem,NewTestCase))
				{
					_Logger.Info("All Attachments Were Migrated");
				}
				else
				{
					_Logger.Warn("There Was An Problem Migrating One Or More Attachments");
				}
				
				return ValidateWorkItem(NewTestCase);
			}
			catch (Exception ex)
			{
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool MigrateTaskToTask(WorkItem sourceWorkItem)
		{
			try
			{
				var NewTask = GetNewWorkItem("Task");
				NewTask = InitializeWorkItem(sourceWorkItem,NewTask);

				NewTask.Fields["Microsoft.VSTS.Scheduling.RemainingWork"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Scheduling.RemainingWork"].Value;
				NewTask.Fields["Microsoft.VSTS.Common.Activity"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Common.Activity"].Value;
				
				if (CopyAttachments(sourceWorkItem, NewTask))
				{
					_Logger.Info("All Attachments Were Migrated");
				}
				else
				{
					_Logger.Warn("There Was An Problem Migrating One Or More Attachments");
				}
				
				return ValidateWorkItem(NewTask);
			}
			catch (Exception ex)
			{
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool MigrateUserStoryToPBI(WorkItem sourceWorkItem)
		{
			try
			{
				var NewProductBacklogItem = GetNewWorkItem("Product Backlog Item");
				NewProductBacklogItem = InitializeWorkItem(sourceWorkItem,NewProductBacklogItem);

				if (CopyAttachments(sourceWorkItem, NewProductBacklogItem))
				{
					_Logger.Info("All Attachments Were Migrated");
				}
				else
				{
					_Logger.Warn("There Was An Problem Migrating One Or More Attachments");
				}
				
				return ValidateWorkItem(NewProductBacklogItem);
			}
			catch (Exception ex)
			{
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool MigrateUserStoryToUserStory(WorkItem sourceWorkItem)
		{
			try
			{
				var NewUserStory = GetNewWorkItem("User Story");
				NewUserStory = InitializeWorkItem(sourceWorkItem, NewUserStory);

				if (CopyAttachments(sourceWorkItem, NewUserStory))
				{
					_Logger.Info("All Attachments Were Migrated");
				}
				else
				{
					_Logger.Warn("There Was An Problem Migrating One Or More Attachments");
				}

				return ValidateWorkItem(NewUserStory);
			}
			catch (Exception ex)
			{
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool MigrateBugToBug(WorkItem sourceWorkItem)
		{
			try
			{
				var NewBug = GetNewWorkItem("Bug");
				NewBug = InitializeWorkItem(sourceWorkItem, NewBug);
				NewBug.Fields["Microsoft.VSTS.Build.FoundIn"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Build.FoundIn"].Value;
				NewBug.Fields["Microsoft.VSTS.Common.Severity"].Value = sourceWorkItem.Fields["Microsoft.VSTS.Common.Severity"].Value;
				NewBug.Fields["Microsoft.VSTS.TCM.ReproSteps"].Value = sourceWorkItem.Fields["Microsoft.VSTS.TCM.ReproSteps"].Value;
				
				if (CopyAttachments(sourceWorkItem, NewBug))
				{
					_Logger.Info("All Attachments Were Migrated");
				}
				else
				{
					_Logger.Warn("There Was An Problem Migrating One Or More Attachments");
				}
				
				return ValidateWorkItem(NewBug);
			}
			catch (Exception ex)
			{
				_Logger.Error(ex.Message);
				return false;
			}
		}

		#endregion

		#region Private Query Generators

		private Query WorkItemQuery(int workItemId, string teamProjectName)
		{
			var varDictionary = new Dictionary<string, object>();
			varDictionary.Add("TPN", teamProjectName);
			varDictionary.Add("WIID", workItemId);

			var wiql = "SELECT * FROM WorkItems WHERE [System.TeamProject] = @TPN AND [System.Id] = @WIID ORDER BY [System.Id]";

			return (new Query(this.ItemStore, wiql, varDictionary));
		}

		private Query WorkItemQuery(string workItemTitle, string teamProjectName)
		{
			var varDictionary = new Dictionary<string,object>();
			varDictionary.Add("TPN", teamProjectName);
			varDictionary.Add("WIT", workItemTitle);

			var wiql = "SELECT * FROM WorkItems WHERE [System.TeamProject] = @TPN AND [System.Title] = @WIT ORDER BY [System.Id]";

			return ( new Query(this.ItemStore,wiql,varDictionary) );
		}

		#endregion

		#region Private Methods

		private bool SetWorkItemState(WITargetInfo targetInfo, string sourceWorkItemState, ProcessTemplateType sourceTemplateType)
		{
			if (targetInfo.TfsWorkItem.Type.Name == "User Story" && targetInfo.TemplateType == ProcessTemplateType.Agile && sourceTemplateType == ProcessTemplateType.Agile)
			{
				if (targetInfo.TfsWorkItem.State == "New")
				{
					if (sourceWorkItemState == "New")
					{
						return true;
					}
					else
					{
						targetInfo.TfsWorkItem.State = "Active";
						if (!ValidateWorkItem(targetInfo.TfsWorkItem))
						{
							_Logger.Error("Failed To Set State To {0} On Work Item {1}", "Active", targetInfo.TfsWorkItem.Id);
							return false;
						}
					}
				}

				if (targetInfo.TfsWorkItem.State == "Active")
				{
					if (sourceWorkItemState == "Active")
					{
						return true;
					}
					else
					{
						targetInfo.TfsWorkItem.State = "Resolved";
						if (!ValidateWorkItem(targetInfo.TfsWorkItem))
						{
							_Logger.Error("Failed To Set State To {0} On Work Item {1}", "Resolved", targetInfo.TfsWorkItem.Id);
							return false;
						}
					}
				}

				if (targetInfo.TfsWorkItem.State == "Resolved")
				{
					if (sourceWorkItemState == "Resolved")
					{
						return true;
					}
					else
					{
						targetInfo.TfsWorkItem.State = "Closed";
						if (!ValidateWorkItem(targetInfo.TfsWorkItem))
						{
							_Logger.Error("Failed To Set State To {0} On Work Item {1}", "Closed", targetInfo.TfsWorkItem.Id);
							return false;
						}
					}
				}

				if (targetInfo.TfsWorkItem.State == "Closed")
				{
					return true;
				}
			}
			else if (targetInfo.TfsWorkItem.Type.Name == "Task" && targetInfo.TemplateType == ProcessTemplateType.Agile && sourceTemplateType == ProcessTemplateType.Agile)
			{
				if (targetInfo.TfsWorkItem.State == "New")
				{
					if (sourceWorkItemState == "New")
					{
						return true;
					}
					else
					{
						targetInfo.TfsWorkItem.State = "Active";
						if (!ValidateWorkItem(targetInfo.TfsWorkItem))
						{
							_Logger.Error("Failed To Set State To {0} On Work Item {1}", "Active", targetInfo.TfsWorkItem.Id);
							return false;
						}
					}
				}

				if (targetInfo.TfsWorkItem.State == "Active")
				{
					if (sourceWorkItemState == "Active")
					{
						return true;
					}
					else
					{
						targetInfo.TfsWorkItem.State = "Closed";
						if (!ValidateWorkItem(targetInfo.TfsWorkItem))
						{
							_Logger.Error("Failed To Set State To {0} On Work Item {1}", "Resolved", targetInfo.TfsWorkItem.Id);
							return false;
						}
					}
				}

				if (targetInfo.TfsWorkItem.State == "Closed")
				{
					return true;
				}
			}
			else if (targetInfo.TfsWorkItem.Type.Name == "Bug" && targetInfo.TemplateType == ProcessTemplateType.Agile && sourceTemplateType == ProcessTemplateType.Agile)
			{
				if (targetInfo.TfsWorkItem.State == "Active")
				{
					if (sourceWorkItemState == "Active")
					{
						return true;
					}
					else
					{
						targetInfo.TfsWorkItem.State = "Resolved";
						if (!ValidateWorkItem(targetInfo.TfsWorkItem))
						{
							_Logger.Error("Failed To Set State To {0} On Work Item {1}", "Resolved", targetInfo.TfsWorkItem.Id);
							return false;
						}
					}
				}

				if (targetInfo.TfsWorkItem.State == "Resolved")
				{
					if (sourceWorkItemState == "Resolved")
					{
						return true;
					}
					else
					{
						targetInfo.TfsWorkItem.State = "Closed";
						if (!ValidateWorkItem(targetInfo.TfsWorkItem))
						{
							_Logger.Error("Failed To Set State To {0} On Work Item {1}", "Closed", targetInfo.TfsWorkItem.Id);
							return false;
						}
					}
				}

				if (targetInfo.TfsWorkItem.State == "Closed")
				{
					return true;
				}
			}
			else
			{
				_Logger.Warn("Setting state on items migrated from a {0} Process Template to a {1} Process Template not yet supported", targetInfo.TemplateType, sourceTemplateType);
			}

			return true;
		}

		private bool DestroyWorkItem(WorkItem sourceWorkItem)
		{
			var result = true;

			try
			{
				var deleteIds = new List<int>() { sourceWorkItem.Id };
				var destroyErrors = this.ItemStore.DestroyWorkItems(deleteIds);

				if (destroyErrors != null && destroyErrors.Count() > 0)
				{
					result = false;
					foreach (var error in destroyErrors)
					{
						_Logger.Error("Destroy Operation Error: {0}", error.Exception.Message);
					}
				}
			}
			catch (Exception ex)
			{
				result = false;
				_Logger.Error("General Exception While Destroying Work Item: {0} Message: {1}", sourceWorkItem.Id, ex.Message);
			}
			
			return result;
		}
		
		private string FormatPath(string currentPath, string part)
		{
			try
			{
				var subPart = part.Substring(part.IndexOf("\\") + 1);
				var newPath = string.Format("{0}\\{1}", currentPath, subPart);
				_Logger.Debug("Formatted Area Path: {0}", newPath);
				return newPath;
			}
			catch
			{
				return currentPath;
			}
		}

		private bool CopyAttachments(WorkItem sourceWorkItem, WorkItem targetWorkItem)
		{
			var result = true;
			if (sourceWorkItem.Attachments.Count > 0)
			{
				using (var client = new WebClient())
				{
					foreach (Attachment attached in sourceWorkItem.Attachments)
					{
						try
						{
							client.UseDefaultCredentials = true;
							client.DownloadFile(attached.Uri, attached.Name);
							targetWorkItem.Attachments.Add(new Attachment(attached.Name, attached.Comment));
						}
						catch (Exception ex)
						{
							_Logger.Error("Failed to Copy Attachment from Uri:{0} with Name:{1}", attached.Uri, attached.Name);
							_Logger.Error(ex.Message);
							result = false;
						}
					}
				}
			}
			return result;
		}

		private bool CopyWorkItemLinks(WorkItem sourceWorkItem, WorkItem targetWorkItem)
		{
			var allLinksCopied = true;
			
			if (sourceWorkItem.Links.Count > 0)
			{
				foreach (Link link in sourceWorkItem.Links)
				{
					switch(link.BaseType)
					{
						case BaseLinkType.WorkItemLink:
						case BaseLinkType.RelatedLink:
							if (SourceInfo.MigrateRelatedWorkItems)
							{
								RelatedLink rLink = link as RelatedLink;
								allLinksCopied = allLinksCopied && AddRelatedLink(rLink, targetWorkItem);
							}
							break;
						case BaseLinkType.ExternalLink:
							ExternalLink eLink = link as ExternalLink;
							allLinksCopied = allLinksCopied && AddExternalLink(eLink, targetWorkItem);
							break;
						case BaseLinkType.Hyperlink:
							Hyperlink hLink = link as Hyperlink;
							allLinksCopied = allLinksCopied && AddHyperLink(hLink, targetWorkItem);
							break;
						default:
							_Logger.Warn("Unknown Link Type Encountered: {0}", link.BaseType);
							break;
					}
				}
			}

			return allLinksCopied;
		}

		private bool AddHyperLink(Hyperlink sourceLink, WorkItem targetWorkItem)
		{
			try
			{
				var alreadyLinked =
					from Hyperlink hyper
					in targetWorkItem.Links
					where hyper.Location == sourceLink.Location
					select hyper;

				if (alreadyLinked.Count() > 0)
				{
					_Logger.Warn("The Work Item:{0} already has a HyperLink to Location:{1}", targetWorkItem.Id, sourceLink.Location);
					return true;
				}
				else
				{
					Hyperlink targetLink = new Hyperlink(sourceLink.Location);
					targetWorkItem.Links.Add(targetLink);
					return ValidateWorkItem(targetWorkItem);
				}
			}
			catch (Exception ex)
			{
				_Logger.Error("The HyperLink to Location:{0} failed to be added to Work Item:{1}", sourceLink.Location, targetWorkItem.Id);
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool AddExternalLink(ExternalLink sourceLink, WorkItem targetWorkItem)
		{
			try
			{
				var alreadyLinked =
					from ExternalLink external
					in targetWorkItem.Links
					where (external.ArtifactLinkType == sourceLink.ArtifactLinkType) && (external.LinkedArtifactUri == sourceLink.LinkedArtifactUri)
					select external;

				if (alreadyLinked.Count() > 0)
				{
					_Logger.Warn("The Work Item:{0} already has an ExternalLink with LinkTypeName:{1} and Uri:{2}",targetWorkItem.Id, sourceLink.ArtifactLinkType.Name, sourceLink.LinkedArtifactUri);
					return true;
				}
				else
				{
					ExternalLink targetLink = new ExternalLink(sourceLink.ArtifactLinkType, sourceLink.LinkedArtifactUri);
					targetWorkItem.Links.Add(targetLink);
					return ValidateWorkItem(targetWorkItem);
				}
			}
			catch(Exception ex)
			{
				_Logger.Error("The ExternalLink with LinkTypeName:{0} and Uri:{1} failed to be added to Work Item:{2}", sourceLink.ArtifactLinkType, sourceLink.LinkedArtifactUri, targetWorkItem.Id);
				_Logger.Error(ex.Message);
				return false;
			}
		}

		private bool AddRelatedLink(RelatedLink sourceLink, WorkItem targetWorkItem)
		{
			var sourceInfo = new WISourceInfo(SourceInfo.TeamProjectName, sourceLink.RelatedWorkItemId, false, SourceInfo.DestroyAfterMigrationSuccess, SourceInfo.TemplateType);
			var targetInfo = new WITargetInfo(TargetInfo.TeamProjectName, TargetInfo.IterationPath, TargetInfo.CreateAreaPath, TargetInfo.SetState, TargetInfo.TemplateType);
			var configuration = new MigrationConfiguration(this.Configuration.ProjectCollectionUri.ToString(),sourceInfo,targetInfo);
			
			var result = false;

			try
			{
				if (new WIMigrator(configuration).Migrate())
				{
					result = true;
					var alreadyLinked =
						from RelatedLink related
						in targetWorkItem.Links
						where related.RelatedWorkItemId == targetInfo.WorkItemId
						select related;

					if (alreadyLinked.Count() > 0)
					{
						_Logger.Warn("The Work Item:{0} already has an RelatedLink with RelatedWorkItemId:{1}", targetWorkItem.Id, sourceLink.RelatedWorkItemId);
					}
					else
					{
						RelatedLink targetLink = new RelatedLink(sourceLink.LinkTypeEnd, targetInfo.WorkItemId);
						targetWorkItem.Links.Add(targetLink);
						result = ValidateWorkItem(targetWorkItem);
					}
				}
			}
			catch (Exception ex)
			{
				_Logger.Error("The RelatedLink with RelatedWorkItemId:{0} failed to be added to Work Item:{2}", sourceLink.RelatedWorkItemId, targetWorkItem.Id);
				_Logger.Error(ex.Message);
				result = false;
			}

			return result;
		}

		private UInt32 CanItemBeMigrated(int sourceWorkItemId, string sourceTeamProjectName, string targetTeamProjectName)
		{
			var result = UInt32.MinValue;
			var wiStore = GetWorkItemStore(TeamProjectCollection);

			try
			{
				#region query source
				var swi =
					from WorkItem wi
					in WorkItemQuery(sourceWorkItemId, sourceTeamProjectName).RunQuery()
					where (wi.Id == sourceWorkItemId) && (wi.Project.Name == sourceTeamProjectName)
					select wi;
				var sourceWorkItem = swi.FirstOrDefault();
				#endregion

				if (sourceWorkItem == null)
				{
					_Logger.Error("The Work Item Id {0} was not found in the source Team Project {1}", sourceWorkItemId, sourceTeamProjectName);
					_Logger.Error("Work Item can not be migrated from Team Project {0} to Team Project {1}", sourceTeamProjectName, targetTeamProjectName);
					result = UInt32.MaxValue;
				}
				else
				{
					#region query target
					var twi =
						from WorkItem wi
						in WorkItemQuery(sourceWorkItem.Title, targetTeamProjectName).RunQuery()
						where (wi.Title == sourceWorkItem.Title) && (wi.Project.Name == targetTeamProjectName)
						select wi;
					WorkItem targetWorkItem = twi.FirstOrDefault();
					#endregion

					if (targetWorkItem != null)
					{
						_Logger.Warn("Team Project {0} contains a Work Item Titled the same as the source {1}", targetTeamProjectName, targetWorkItem.Title);
						_Logger.Warn("Work Item Id {2} already migrated from Team Project {0} to Team Project {1} with Id {3}", sourceTeamProjectName, targetTeamProjectName, sourceWorkItemId, targetWorkItem.Id);
						result = (UInt32) targetWorkItem.Id;
					}
				}

				return result;
			}
			catch(Exception ex)
			{
				_Logger.Error(ex.Message);
				return UInt32.MaxValue;
			}
		}

		private bool ValidateWorkItem(WorkItem targetWorkItem)
		{
			if (targetWorkItem.IsValid())
			{
				try
				{
					targetWorkItem.Save(SaveFlags.None);
					this.TargetInfo.WorkItemId = targetWorkItem.Id;
				}
				catch (Exception ex)
				{
					_Logger.Fatal(ex.Message);
				}
				return true;
			}
			else
			{
				var result = false;
				var reasons = targetWorkItem.Validate();

				if ((reasons != null) && (reasons.Count == 1))
				{
					var reason = reasons[0] as Field;
					if (reason.Name == "Area Path" && reason.Status == FieldStatus.InvalidPath && reason.Value.ToString() != targetWorkItem.Project.Name)
					{
						targetWorkItem.AreaPath = targetWorkItem.Project.Name.Trim();
						result = ValidateWorkItem(targetWorkItem);
					}
					else
					{
						_Logger.Warn("Could not validate New Work Item (Source Work Item Id: {0})", SourceInfo.WorkItemId);
						_Logger.Warn("Field: {0}, Status: {1}, Value: {2}", reason.Name, reason.Status, reason.Value);
					}
				}
				else
				{
					_Logger.Warn("Could not validate New Work Item (Source Work Item Id: {0})", SourceInfo.WorkItemId);
					foreach (Field reason in reasons)
					{
						_Logger.Warn("Field: {0}, Status: {1}, Value: {2}", reason.Name, reason.Status, reason.Value);
					}
				}

				return result;
			}
		}
		
		private string BuildRevisionHistory(RevisionCollection revisions)
		{
			var query = from Revision r in revisions orderby r.Fields["Changed Date"].Value descending select r;
			StringBuilder stringBuilder = new StringBuilder(
				string.Format(
					"Work Item Id {0} migrated from Team Project {1} to Team Project {2}<BR />\n", 
					this.SourceInfo.WorkItemId, 
					this.SourceInfo.TeamProjectName, 
					this.TargetInfo.TeamProjectName
				)
			);

			foreach (Revision revision in query)
			{
				String history = (String)revision.Fields["History"].Value;
				if (!string.IsNullOrEmpty(history))
				{
					foreach (Field field in revision.Fields)
					{
						if (!Object.Equals(field.Value, field.OriginalValue))
						{
							if (field.Name == "History")
							{
								var notation = string.Empty;
								if (revision.Fields["State"].OriginalValue != revision.Fields["State"].Value)
								{
									notation = String.Format("({0} to {1})", revision.Fields["State"].OriginalValue, revision.Fields["State"].Value);
								}

								stringBuilder.AppendFormat(
									"<STRONG>{0} Edited {3} by {1}</STRONG><BR />\n{2}<BR />\n", 
									revision.Fields["Changed Date"].Value, 
									revision.Fields["Changed By"].Value, 
									field.Value, 
									notation
								);
							}
						}
					}
				}
			}
			return stringBuilder.ToString();
		}

		public PathStructureStatus EnsureAreaPathExists(string targetTeamProjectName, string areaPath)
		{
			PathStructureStatus result = PathStructureStatus.Exists;

			areaPath = areaPath.Substring(areaPath.IndexOf("\\") + 1);
			var areaParts = areaPath.Split('\\');
			
			var commonStructureService = GetCommonStructureService(this.TeamProjectCollection);
			var projectInfo = commonStructureService.GetProjectFromName(targetTeamProjectName);
			var parentNode = commonStructureService.ListStructures(projectInfo.Uri).FirstOrDefault(node => node.Name == "Area");
			var parentNodeUri = parentNode.Uri;
			var currentPath = FormatPath(targetTeamProjectName, "Area");
			
			foreach (var part in areaParts)
			{
				if (!string.IsNullOrEmpty(part))
				{
					currentPath = FormatPath(currentPath, part);

					try
					{
						var currentNode = commonStructureService.GetNodeFromPath(currentPath);
						parentNodeUri = currentNode.Uri;
						_Logger.Debug("The Area {0} Exists", currentNode.Path);
					}
					catch
					{
						try
						{
							parentNodeUri = commonStructureService.CreateNode(part, parentNodeUri);
							_Logger.Debug("The Area {0} Created", currentPath);
							result = PathStructureStatus.Created;
						}
						catch (Exception ex)
						{
							_Logger.Error("Area Creation Exception {0}", ex.Message);
							this.TargetInfo.CreateAreaPath = false;
							result = PathStructureStatus.Unknown;
						}
					}
				}
			}

			return result;
		}

		public PathStructureStatus EnsureIterationPathExists(string iterationPath)
		{
			PathStructureStatus result = PathStructureStatus.Exists;

			var teamProject = iterationPath.Split('\\')[0];
			iterationPath = iterationPath.Substring(iterationPath.IndexOf("\\") + 1);
			var pathParts = iterationPath.Split('\\');

			var commonStructureService = GetCommonStructureService(this.TeamProjectCollection);
			var projectInfo = commonStructureService.GetProjectFromName(teamProject);
			var parentNode = commonStructureService.ListStructures(projectInfo.Uri).FirstOrDefault(node => node.Name == "Iteration");

			var parentNodeUri = parentNode.Uri;
			var currentPath = FormatPath(teamProject, "Iteration");

			foreach (var part in pathParts)
			{
				if (!string.IsNullOrEmpty(part))
				{
					currentPath = FormatPath(currentPath, part);

					try
					{
						var currentNode = commonStructureService.GetNodeFromPath(currentPath);
						parentNodeUri = currentNode.Uri;
						_Logger.Debug("The Iteration {0} Exists", currentNode.Path);
					}
					catch
					{
						try
						{
							parentNodeUri = commonStructureService.CreateNode(part, parentNodeUri);
							_Logger.Debug("The Iteration {0} Created", currentPath);
							result = PathStructureStatus.Created;
						}
						catch (Exception ex)
						{
							_Logger.Error("Iteration Creation Exception {0}", ex.Message);
							this.TargetInfo.IterationPath = string.Empty;
							result = PathStructureStatus.Unknown;
						}
					}
				}
			}

			return result;
		}
		
		#endregion

		#region Fields

		private bool _ReadyToMigrate;
		private SLogger _Logger;
		private int _MigrationErrorCount;
		private List<int> _ProcessedIds;

		#endregion

		#region Properties

		private MigrationConfiguration _Configuration;
		public MigrationConfiguration Configuration 
		{ 
			get { return this._Configuration; } 
		}

		private Uri _ProjectCollectionUri;
		public Uri ProjectCollectionUri 
		{
			get { return this._ProjectCollectionUri; }
		}

		public TfsTeamProjectCollection TeamProjectCollection { get; set; }

		public WorkItemStore ItemStore { get; set; }

		public Project SourceTeamProject { get; set; }

		public Project TargetTeamProject { get; set; }

		public WISourceInfo SourceInfo { get; set; }

		public WITargetInfo TargetInfo { get; set; }
		
		#endregion Properties
	}
}
